package vg.modules.opener.system.graphml;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.StringEscapeUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import vg.modules.opener.system.graphml.elements.GraphMLAttribute;
import vg.modules.opener.system.graphml.elements.GraphMLGraph;
import vg.modules.opener.system.graphml.elements.GraphMLVertex;
import vg.services.data_base_manager.data.header.GraphModelHeader;
import vg.services.data_base_manager.data.header.VertexHeader;
import vg.services.data_base_manager.interfaces.IModel;
import vg.services.data_base_manager.realization.DataBaseTypeParser;
import vg.services.main_manager.MainManager;

class GraphMLParser extends DefaultHandler {
    private IModel model;
    private GraphModelHeader graph_header;
    private boolean first_subgraph = false;
    // schema parsing
    private String m_key;
    private String m_id;
    private String m_for;
    private String m_name;
    private String m_type;
    private String m_dflt;
    //private String error;
    private StringBuffer m_sbuf = new StringBuffer();
  // node, edge, data parsing
    private boolean inSchema;
    private boolean inSubGraphSection = false;
    private HashMap<String, GraphMLAttribute> m_vertData = new HashMap<String, GraphMLAttribute>();
    private HashMap<String, GraphMLAttribute> m_edgeData = new HashMap<String, GraphMLAttribute>();
    
    private final Stack<GraphMLGraph> lastSubGraph = new Stack<GraphMLGraph>();
    private final Stack<Integer> lastNodes = new Stack<Integer>();
    private final Stack<Integer> lastEdges = new Stack<Integer>();
    private final Stack<List<GraphMLAttribute>> lastAttributes = new Stack<List<GraphMLAttribute>>();
//=============================================================================

    
    GraphMLParser(IModel model, String graphName) {
        this.model = model;
        this.graph_header = new GraphModelHeader(model.createGraphModelHeader(graphName));
        this.graph_header.name = graphName;
    }

    @Override
    public void startDocument() {
        inSchema = true;
    }

    @Override
    public void endDocument() throws SAXException {
        // if we don't reach the end of document, delete graph from model
    }

    @Override
    public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
        // first clear the character buffer.
        m_sbuf.setLength(0);

        // FIX ME. If we interchange 'if', we have get increase, 
        // because label 'GRAPH' less common than 'NODE' or 'EDGE'.
        // If you read it, make it. And write test which show it=)
        if (qName.equals(GraphMLTokens.GRAPH)) {
            // parse directedness default
            String edef = atts.getValue(GraphMLTokens.EDGEDEF);
            boolean directed = GraphMLTokens.DIRECTED.equalsIgnoreCase(edef);
            String str_id = atts.getValue(GraphMLTokens.ID);
            
            int id = model.createGraphHeader(graph_header.id, directed);
            GraphMLGraph tmp_sg = new GraphMLGraph(id, graph_header.id, str_id, directed);
            
            if(!first_subgraph) {
                graph_header.root_id = id;
                model.modifyGraphModelHeader(graph_header);
                first_subgraph = true;
            }
            
            // add link to inner graph
            if(lastNodes.size() > 0) {
                Integer v_id = lastNodes.peek();
                VertexHeader vh = model.getVertexHeader(v_id);
                vh.link2inner_graph = id;
                model.modifyVertexHeader(vh);
            }
            
            lastSubGraph.push(tmp_sg);
            lastAttributes.add(new ArrayList<GraphMLAttribute>());
            inSubGraphSection = true;
        } else if (qName.equals(GraphMLTokens.KEY)) {
            if (!inSchema) {
                error("\"" + GraphMLTokens.KEY + "\" elements can not" + " occur after the first node or edge declaration.");
            }
            m_for = atts.getValue(GraphMLTokens.FOR);
            m_id = atts.getValue(GraphMLTokens.ID);
            m_name = atts.getValue(GraphMLTokens.ATTRNAME);
            m_type = atts.getValue(GraphMLTokens.ATTRTYPE);
        } else if (qName.equals(GraphMLTokens.NODE)) {
            inSchema = false;
            inSubGraphSection = false;
            String str_id = atts.getValue(GraphMLTokens.ID);
            int sg_id = lastSubGraph.peek().getId();
            int v_id = model.createVertexHeader(sg_id, -1);
            lastSubGraph.peek().addVertex(v_id, str_id);
            lastAttributes.add(new ArrayList<GraphMLAttribute>());
            for (GraphMLAttribute attr : m_vertData.values()) {
                if (attr.getValue() != null) {
                    lastAttributes.peek().add(new GraphMLAttribute(attr.getName(), attr.getType(), attr.getValue()));
                }
            }
            lastNodes.add(v_id);
            
        } else if (qName.equals(GraphMLTokens.EDGE)) {
            inSchema = false;
            inSubGraphSection = false;

            //String str_id = atts.getValue(ID);
            String left = atts.getValue(GraphMLTokens.SOURCE);
            String right = atts.getValue(GraphMLTokens.TARGET);
            
            GraphMLGraph tmp_sg = lastSubGraph.peek();
            
            GraphMLVertex v1 = null, v2 = null;
            for(GraphMLVertex buf : tmp_sg.getVertices()) {
                String id = buf.getStr_id();
                if(id != null && id.equals(left)) {
                    v1 = buf;
                }
                if(id != null && id.equals(right)) {
                    v2 = buf;
                }
            }
            
            int e_id = -1;
            lastAttributes.add(new ArrayList<GraphMLAttribute>());
            if(v1 != null && v2 != null) {
                e_id = model.quickCreateEdgeHeader(tmp_sg.getId(), v1.getId(), v2.getId());
                for (GraphMLAttribute attr : m_edgeData.values()) {
                    if (attr.getValue() != null) {
                        lastAttributes.peek().add(new GraphMLAttribute(attr.getName(), attr.getType(), attr.getValue()));
                    }
                }
            } else {
                MainManager.log.printInfo("Skip edge between different nested graphs. Left = " + left + " Right = " + right);
            }
            lastEdges.add(e_id);
        } else if (qName.equals(GraphMLTokens.DATA)) {
            m_key = atts.getValue(GraphMLTokens.KEY);
        }
    }

    @Override
    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
        if (qName.equals(GraphMLTokens.DEFAULT)) {
            // value is in the buffer
            m_dflt = m_sbuf.toString();
        } else if (qName.equals(GraphMLTokens.KEY)) {
            // time to add to the proper schema(s)
            addToSchema();
        } else if (qName.equals(GraphMLTokens.GRAPH)) {
            //NestedGraph e = lastGraph.pop();
            lastSubGraph.pop();
            inSubGraphSection = false;
            lastAttributes.pop();
        } else if (qName.equals(GraphMLTokens.NODE)) {
            inSubGraphSection = true;
            int v_id = lastNodes.pop();
            List<GraphMLAttribute> list_a = lastAttributes.pop();
            for (GraphMLAttribute buf : list_a) {
                model.createAttributeHeader4Vertex(v_id, buf.getName(), buf.getValue(), buf.getType());
            }
        } else if (qName.equals(GraphMLTokens.EDGE)) {
            inSubGraphSection = true;
            int e_id = lastEdges.pop();
            List<GraphMLAttribute> list_a = lastAttributes.pop();
            if(e_id >= 0) {
                for(GraphMLAttribute buf : list_a) {
                    model.createAttributeHeader4Edge(e_id, buf.getName(), buf.getValue(), buf.getType());
                }
            }
        } else if (qName.equals(GraphMLTokens.DATA)) {
            // value is in the buffer
            String value = StringEscapeUtils.unescapeJava(m_sbuf.toString());

            if (inSubGraphSection) {
                // name of subgraph
            } else {
                //Class<?> type;
                GraphMLAttribute a = m_vertData.get(m_key);
                if (a == null)
                    a = m_edgeData.get(m_key);
                if (a == null)
                    error("found data tag in wrong position");
                try {
                    Object val = DataBaseTypeParser.string2object(value, a.getType());
                    boolean newattr = true;
                    for (GraphMLAttribute attr : lastAttributes.lastElement()) {
                        if (attr.getName().equals(a.getName())) {
                            attr.setValue(val);
                            newattr = false;
                        }
                    }
                    if (newattr)
                        lastAttributes.lastElement().add(new GraphMLAttribute(a.getName(), a.getType(), val));
                } catch (ParseException ex) {
                    Logger.getLogger(GraphMLDecoder.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        m_sbuf.append(ch, start, length);
    }
    
    public Integer getGraphID() throws Exception {
        return graph_header.id;
    }
//=============================================================================
//-----------------------PRIVATE METHODS---------------------------------------
    private void addToSchema() {
        if (m_name == null || m_name.length() == 0) {
            error("Empty " + GraphMLTokens.KEY + " name.");
        }
        if (m_type == null || m_type.length() == 0) {
            error("Empty " + GraphMLTokens.KEY + " type.");
        }

        Class<?> type = DataBaseTypeParser.string2class(m_type);
        Object dflt = null;
        try {
            dflt = m_dflt == null ? null : DataBaseTypeParser.string2object(m_dflt, type);
        } catch (ParseException ex) {
            Logger.getLogger(GraphMLDecoder.class.getName()).log(Level.SEVERE, null, ex);
        }

        if (m_for == null || m_for.equals(GraphMLTokens.ALL)) {
            m_vertData.put(m_id, new GraphMLAttribute(m_name, type, dflt));
            m_edgeData.put(m_id, new GraphMLAttribute(m_name, type, dflt));
        } else if (m_for.equals(GraphMLTokens.NODE)) {
            m_vertData.put(m_id, new GraphMLAttribute(m_name, type, dflt));
        } else if (m_for.equals(GraphMLTokens.EDGE)) {
            m_edgeData.put(m_id, new GraphMLAttribute(m_name, type, dflt));
        } else {
            error("Unrecognized \"" + GraphMLTokens.FOR + "\" value: " + m_for);
        }

        m_dflt = null;

    }

    private void error(String s) {
        throw new RuntimeException(s);
    }

} // end of inner class GraphMLHandler